#include "Surface.h"

Eigenstructure* Surface::eigenstructures = 0;

void Surface::useEigenstructures(Eigenstructure* e) {
    eigenstructures = e;
}

Surface::Surface() {
    level = 0;
}

Surface::Surface(const char* filename) {
    level = 0;
    ifstream infile;

    char buffer[bufferSize];
    SurfaceInputState state = SurfaceInputState();

    infile.open(filename, ifstream::in);

    while (infile.good()) {
        infile.getline(buffer, bufferSize);
        processLine(buffer, state);
    }

    infile.close();
}

//macro for readability
#define PROCESS_OP(op_name) \
    if (!strcmp(op, #op_name)) { \
        op_ ## op_name ## (ss, state); \
    }

void Surface::processLine(const char* s, SurfaceInputState& state) {
    stringstream ss(s);
    
    char op[bufferSize];

    ss >> op;

    if (ss.fail()) return;

    PROCESS_OP(v);
    PROCESS_OP(e);
    PROCESS_OP(f);
    PROCESS_OP(sharpness);
    PROCESS_OP(opt);
}

void Surface::op_v(stringstream& ss, SurfaceInputState& state) {
    double x, y, z;
    ss >> x >> y >> z;
    Vector3d pos(x, y, z);
    addVertex(pos, state.sharpness, state.opt);
}

void Surface::op_e(stringstream& ss, SurfaceInputState& state) {
    int a, b;
    ss >> a >> b;
    addEdge(a - 1, b - 1, state.sharpness, state.opt);
}

void Surface::op_f(stringstream& ss, SurfaceInputState& state) {
    vector<int> vertexIndices;
    char vertexCString[bufferSize];
    while (ss.good()) {
        ss >> vertexCString;
        string vertexString(vertexCString);
        size_t slashpos = vertexString.find("/");
        vertexString = vertexString.substr(0, slashpos);
        int vertexIndex = atoi(vertexString.c_str()) - 1;
        vertexIndices.push_back(vertexIndex);
    }

    addFace(vertexIndices, state.sharpness, state.opt);
}

void Surface::op_sharpness(stringstream& ss, SurfaceInputState& state) {
    int sharpness;
    ss >> sharpness;
    state.sharpness = sharpness;
}

void Surface::op_opt(stringstream& ss, SurfaceInputState& state) {
    int opt;
    ss >> opt;
    state.opt = (opt != 0);
}

void Surface::calculateCurvatures() {
    for (unsigned int i = 0; i < vertices.size(); i++) {
        vertices[i]->sort();
    }

    if (level > 0) {
        for (unsigned int i = 0; i < vertices.size(); i++) {
            vertices[i]->calculateCurvatures();
        }
    }
}

void Surface::writeObj(const char* filename, bool extended, double curvatureScale) const {
    ofstream outfile;
    outfile.open(filename, ofstream::out);

    SurfaceInputState state = SurfaceInputState();

    outfile << "o subdivision\n";

    //vertex positions
    for (unsigned int i = 0; i < vertices.size(); i++) {
        if (vertices[i]->sharpness != state.sharpness) {
            outfile << "sharpness " << vertices[i]->sharpness << "\n";
            state.sharpness = vertices[i]->sharpness;
        }
        outfile << "v " << vertices[i]->pos << "\n";
    }

    //vertex tex coords (curvature)
    for (unsigned int i = 0; i < vertices.size(); i++) {
        outfile << "vt ";
        outfile << vertices[i]->gaussianCurvature;
        outfile << " ";
        outfile << vertices[i]->meanCurvature;
        outfile << "\n";
    }

    //vertex normals
    for (unsigned int i = 0; i < vertices.size(); i++) {
        outfile << "vn " << vertices[i]->normal << "\n";

        //test import
        //outfile << "vn " << "1 0 0" << "\n";
    }

    if (extended) {
        for (unsigned int i = 0; i < edges.size(); i++) {
            if (edges[i]->sharpness != state.sharpness) {
                outfile << "sharpness " << edges[i]->sharpness << "\n";
                state.sharpness = edges[i]->sharpness;
            }
            outfile << "e " << edges[i]->startVertex->index + 1 << " " << edges[i]->endVertex->index + 1 << "\n";
        }
    }

    for (unsigned int i = 0; i < faces.size(); i++) {
        Face* f = faces[i];
        if (f->sharpness != state.sharpness) {
            outfile << "sharpness " << f->sharpness << "\n";
            state.sharpness = f->sharpness;
        }

        outfile << "f";

        for (unsigned int j = 0; j < f->vertices.size(); j++) {
            int printIndex = f->vertices[j]->index + 1;
            outfile << " " << printIndex << "/" << printIndex << "/" << printIndex;
        }
        outfile << "\n";
    }

    outfile.close();
}

Surface::~Surface() {
    for (unsigned int i = 0; i < vertices.size(); i++) {
        delete vertices[i];
    }

    for (unsigned int i = 0; i < edges.size(); i++) {
        delete edges[i];
    }

    for (unsigned int i = 0; i < faces.size(); i++) {
        delete faces[i];
    }
}

void Surface::addVertex(const Vector3d& pos, int sharpness, bool opt) {
    Vertex* v = new Vertex(vertices.size(), pos, sharpness, opt);
    vertices.push_back(v);
}

void Surface::addVertex(Vertex* v) {
    v->index = vertices.size();
    vertices.push_back(v);
}

void Surface::addEdge(Edge* e) {
    e->index = edges.size();
    edges.push_back(e);
}

Edge* Surface::addEdge(int a, int b, int sharpness, bool opt) {
    Vertex* vertexA = vertices[a];
    Vertex* vertexB = vertices[b];
    Edge* edge = new Edge(edges.size(), vertices[a], vertices[b], sharpness, opt);
    edges.push_back(edge);
    vertexA->addEdge(edge);
    vertexB->addEdge(edge);
    return edge;
}

void Surface::setEdge(int a, int b, Face* face) {
    Vertex* vertexA = vertices[a];
    Vertex* vertexB = vertices[b];
    Edge* edge = vertexA->getEdge(a, b);
    if (!edge) {
        edge = addEdge(a, b, 0, face->opt);
    }

    if (edge->startVertex->index == a) {
        edge->forwardFace = face;
    } else {
        edge->reverseFace = face;
    }
}

void Surface::addFace(const vector<int>& vertexIndices, int sharpness, bool opt) {
    unsigned int n = vertexIndices.size();

    vector<Vertex*> faceVertices;

    for (unsigned int i = 0; i < n; i++) {
        unsigned int vertexIndex = vertexIndices[i];
        Vertex* vertex = vertices[vertexIndex];
        faceVertices.push_back(vertex);
    }

    addFace(faceVertices, sharpness, opt);
}

void Surface::addFace(const vector<Vertex*>& faceVertices, int sharpness, bool opt) {
    unsigned int n = faceVertices.size();
    Face* face = new Face(faces.size(), faceVertices, sharpness, opt);

    faces.push_back(face);

    for (unsigned int i = 0; i < n; i++) {
        Vertex* vertexA = faceVertices[i];
        vertexA->addFace(face);
        Vertex* vertexB = faceVertices[(i + 1) % n];
        unsigned int a = vertexA->index;
        unsigned int b = vertexB->index;

        setEdge(a, b, face);
    }
}

Surface* Surface::cc() const {
    Surface* result = new Surface();
    result->level = level + 1;
    for (unsigned int i = 0; i < vertices.size(); i++) {
        Vertex* vertex = new Vertex();
        vertices[i]->setCCVertex(vertex);
        result->addVertex(vertex);
    }

    for (unsigned int i = 0; i < edges.size(); i++) {
        Vertex* vertex = new Vertex();
        Edge* startEdge = new Edge();
        Edge* endEdge = new Edge();
        edges[i]->setCC(vertex, startEdge, endEdge);
        result->addVertex(vertex);
        result->addEdge(startEdge);
        result->addEdge(endEdge);
    }

    for (unsigned int i = 0; i < faces.size(); i++) {
        Vertex* vertex = new Vertex();
        Face* face = faces[i];
        face->setCCVertex(vertex);
        result->addVertex(vertex);

        unsigned int n = face->vertices.size();

        for (unsigned int j = 0; j < n; j++) {
            Vertex* a = face->vertices[j];
            Vertex* b = face->vertices[(j + 1) % n];
            Vertex* c = face->vertices[(j + 2) % n];
            Edge* e0 = b->getEdge(a->index, b->index);
            Edge* e1 = b->getEdge(b->index, c->index);

            vector<Vertex*> quadVertices;
            quadVertices.push_back(face->ccVertex);
            quadVertices.push_back(e0->ccVertex);
            quadVertices.push_back(b->ccVertex);
            quadVertices.push_back(e1->ccVertex);

            result->addFace(quadVertices, 0, face->opt);
        }
    }

    return result;
}

Surface* Surface::ccLimit() const {
    Surface* result = new Surface();
    result->level = level + 1;
    for (unsigned int i = 0; i < vertices.size(); i++) {
        Vertex* vertex = new Vertex();
        vertices[i]->setCCLimitVertex(vertex);
        result->addVertex(vertex);
    }


    for (unsigned int i = 0; i < faces.size(); i++) {
        vector<int> faceIndices;
        for (unsigned int j = 0; j < faces[i]->vertices.size(); j++) {
            faceIndices.push_back(faces[i]->vertices[j]->index);
        }
        result->addFace(faceIndices, 0, faces[i]->opt);
    }

    return result;
}

void Surface::surfaceGeometry(bool useMeanCurvature) const {
    glColor3f(1.0, 1.0, 1.0);
    if (level > 0) {
        glBegin(GL_QUADS);
            for (unsigned int i = 0; i < faces.size(); i++) {
                faces[i]->geometry(useMeanCurvature);
            }
        glEnd();
    } else {
        for (unsigned int i = 0; i < faces.size(); i++) {
            glBegin(GL_POLYGON);
                faces[i]->geometry(useMeanCurvature);
            glEnd();
        }
    }
}

void Surface::normalGeometry() const {
    glBegin(GL_LINES);
    for (unsigned int i = 0; i < vertices.size(); i++) {
        vertices[i]->normalGeometry(0.125);
    }
    glEnd();
}

void Surface::opt(const char* optType, int n) {
    if (!strcmp(optType, "centroid")) {
        for (int i = 0; i < n; i++) {
            Vertex* v = vertices[i % vertices.size()];
            if (v->opt) optCentroid(v);
        }
    } else if (!strcmp(optType, "surfaceArea")) {
        for (int i = 0; i < n; i++) {
            Vertex* v = vertices[i % vertices.size()];
            if (v->opt) optSurfaceArea(v);
        }
        for (unsigned int i = 0; i < vertices.size(); i++) {
            vertices[i]->calculateCurvatures();
        }
    } else if (!strcmp(optType, "meanCurvature")) {
        for (int i = 0; i < n; i++) {
            Vertex* v = vertices[i % vertices.size()];
            if (v->opt) optMeanCurvature(v);
        }
        for (unsigned int i = 0; i < vertices.size(); i++) {
            vertices[i]->calculateCurvatures();
        }
    }
}
